Skip to content

Comments

Support unnamed structs/unions/enums#9

Open
davispuh wants to merge 1 commit intoMVV90:mainfrom
davispuh:unnamed
Open

Support unnamed structs/unions/enums#9
davispuh wants to merge 1 commit intoMVV90:mainfrom
davispuh:unnamed

Conversation

@davispuh
Copy link

@davispuh davispuh commented Feb 2, 2026

Currently if you try to generate mappings that contain unnamed/anonymous C structs, unions or enums then it will generate invalid Ruby code.
For example this C code:

union _cairo_path_data_t {
    struct {
        cairo_path_data_type_t type;
        int length;
    } header;
    struct {
        double x, y;
    } point;
};

will produce this Ruby code:

  class Struct (unnamed at test/headers/cairo/cairo.h:1882:5) < FFI::Struct
    layout :type, :path_data_type,
           :length, :int
  end

Which is not valid Ruby.
Same issue for:

typedef struct _LIBSSH2_POLLFD {
    unsigned char type; /* LIBSSH2_POLLFD_* below */
    union {
        int socket; /* File descriptors -- examined with system select() call */
        LIBSSH2_CHANNEL *channel; /* Examined by checking internal state */
        LIBSSH2_LISTENER *listener; /* Read polls only -- are inbound
                                       connections waiting to be accepted? */
    } fd;
    unsigned long events; /* Requested Events */
    unsigned long revents; /* Returned Events */
} LIBSSH2_POLLFD;
  class Union (unnamed at test/headers/libssh2.h:272:5) < FFI::Union
    layout :socket, :int,
           :channel, LIBSSH2CHANNEL,
           :listener, LIBSSH2LISTENER
  end  

And

enum {
/*! @constant kEDAssemblySyntaxX86Intel Intel syntax for i386 and x86_64. */
  kEDAssemblySyntaxX86Intel  = 0,
/*! @constant kEDAssemblySyntaxX86ATT AT&T syntax for i386 and x86_64. */
  kEDAssemblySyntaxX86ATT    = 1,
  kEDAssemblySyntaxARMUAL    = 2
};
  enum :enum (unnamed at test/headers/llvm-c/_enhanced_disassembly.h:54:1), [
    :k_ed_assembly_syntax_x86_intel, 0,
    :k_ed_assembly_syntax_x86att, 1,
    :k_ed_assembly_syntax_armual, 2
  ]

This PR fixes these issues so these unnamed items will still get name.
For example this is how they'll look like

  # Struct example
  class PathDataT1212550491 < FFI::Struct
    layout :type, :path_data_type,
           :length, :int
  end

  class PathDataT < FFI::Union
    layout :header, PathDataT1212550491.by_value,
           :point, PathDataT2575611410.by_value
  end

  # Union example
  class LIBSSH2POLLFD697836898 < FFI::Union
    layout :socket, :int,
           :channel, LIBSSH2CHANNEL,
           :listener, LIBSSH2LISTENER
  end

  class LIBSSH2POLLFD < FFI::Struct
    layout :type, :uchar,
           :fd, LIBSSH2POLLFD697836898.by_value,
           :events, :ulong,
           :revents, :ulong
  end

  # Enum example
  enum :k_ed_assembly_syntax_x86_intel3874185085, [
    :x86_intel, 0,
    :x86att, 1,
    :armual, 2
  ]

Copilot AI review requested due to automatic review settings February 2, 2026 17:03
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes invalid Ruby output when generating bindings for anonymous/unnamed C structs, unions, and enums by ensuring such declarations receive a usable synthesized name.

Changes:

  • Add zlib dependency for hashing.
  • Update Clang::Cursor#spelling to detect clang’s (unnamed at ...) spellings for struct/union/enum cursors.
  • Generate a deterministic replacement name using the cursor USR plus a CRC32 hash.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +93 to +94
prefix = usr.split('@')[2]
prefix = 'unnamed' unless prefix
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback prefix = 'unnamed' unless prefix doesn’t cover cases where usr.split('@')[2] returns an empty string (or a numeric-only segment). In that case spelling can become something like "123…", which later turns into a Ruby class/constant name starting with _ (e.g. _123…) and is still invalid for class/FFI::Struct constants. Please treat nil and empty/invalid prefixes the same (e.g., default to a leading alpha prefix like unnamed), so the synthesized identifier is always a valid Ruby constant/symbol.

Suggested change
prefix = usr.split('@')[2]
prefix = 'unnamed' unless prefix
prefix = usr.split('@')[2].to_s
prefix = 'unnamed' if prefix.empty? || prefix !~ /\A[[:alpha:]]/

Copilot uses AI. Check for mistakes.
Comment on lines 88 to +96
def spelling
String.from_c(C.get_cursor_spelling(@c)).to_s
spelling = String.from_c(C.get_cursor_spelling(@c)).to_s
# Fix names for unnamed items
if [:struct_decl, :union_decl, :enum_decl].include?(kind) && spelling.match?(/(struct|union|enum) \(unnamed at/)
usr = String.from_c(C.get_cursor_usr(@c)).to_s
prefix = usr.split('@')[2]
prefix = 'unnamed' unless prefix
spelling = prefix + Zlib::crc32(usr).to_s
end
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Synthesizing a non-empty Cursor#spelling for unnamed structs/unions/enums will prevent downstream renaming paths that rely on name.nil? (e.g., nested anonymous records get renamed from the containing type + field name in generator.rb:318, and anonymous records/enums under typedefs get renamed in generator.rb:417). This can cause large, breaking output-name changes (and less readable names) even when a better name is available. Consider moving the "unnamed" handling to the generator (or otherwise allowing typedef/field-based renames to override), and only mint a synthetic name when the declaration would otherwise remain unnamed in the final output.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant